﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework.Internal;
using NHibernate.Criterion;
using Pampa.CRUD.Core;
using Pampa.CRUD.Exceptions;

namespace Pampa.MVC.Models
{
    public class PampaViewModel<T> : IPampaViewModel
    {
        private T _entity;

        public PampaViewModel()
        {
            _entity = Activator.CreateInstance<T>();
        }

        public PampaViewModel(T entity)
        {
            this.Entity = entity;
        }

        public T Entity
        {
            get { return _entity; }
            set { _entity = value; }
        }

        public object GetInnerEntity()
        {
            return _entity;
        }

        public void SetInnerEntity(object entity)
        {
            _entity = (T)entity;
        }

        #region IGenericEntity

        public IList GetPropertyList(string propertyID, Type itemType)
        {
            try
            {
                // if itemType is a PampaMvcViewModel then I get the list and wrap everyitem in a PampaMvcViewModel instance
                if (typeof(IPampaViewModel).IsAssignableFrom(itemType))
                {
                    return GetPropertyViewModelList(propertyID, itemType);
                }
                else
                {
                    return (IList)GetPropertyValue(propertyID);
                }
            }
            catch (Exception)
            {
                throw new EntityManagerException("Unexpected error trying to create a list of " + itemType.Name);
            }
        }

        public object GetPropertyValue(string propertyID)
        {
            try
            {
                return EntityManager.GetPropertyValue(_entity, propertyID);
            }
            catch (EntityManagerException)
            {
                return GetPropertyValueAux(propertyID);
            }
        }

        public void SetPropertyValue(string propertyID, object value)
        {
            if (IsViewModelList(value))
            {
                SetPropertyViewModelList(value, propertyID);
            }
            else if (value is IPampaViewModel)
            {
                EntityManager.SetPropertyValue(_entity, propertyID, ((IPampaViewModel)value).GetInnerEntity());
            }
            else
            {
                EntityManager.SetPropertyValue(_entity, propertyID, value);
            }
        }

        public void SetPropertyValueFromString(string propertyID, string strValue)
        {
            EntityManager.SetPropertyValueFromString(_entity, propertyID, strValue, null);
        }

        #endregion

        #region ICustomPersistence

        public virtual bool IsNew
        {
            get { return false; }
        }

        public virtual bool Visible
        {
            get { return true; }
        }

        public virtual void Validate()
        {
            // by default nothing to validate
        }

        public virtual void CustomDelete()
        {
            ActiveRecordMediator.Delete(_entity);
        }

        public virtual void CustomSave()
        {
            ActiveRecordMediator.Save(_entity);
        }

        #endregion

        #region Private Functions

        private object GetPropertyValueAux(string propertyID)
        {
            var property = this.GetType().GetProperty(propertyID);

            if (property != null)
            {
                return property.GetValue(this, BindingFlags.FlattenHierarchy, null, null, null);
            }
            else
            {
                return null;
            }
        }

        private IList GetPropertyViewModelList(string propertyID, Type itemType)
        {
            // if itemType is a PampaMvcViewModel then I get the list and wrap everyitem in a PampaMvcViewModel instance
            // so at the end I get a list of elements that implement IActiveRecord
            var viewModelsList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { itemType }));

            var list = (IList)EntityManager.GetPropertyValue(_entity, propertyID);
            if (list != null)
            {
                foreach (var item in list)
                {
                    var viewModel = Activator.CreateInstance(itemType);
                    var entityProperty = itemType.GetProperty("Entity");
                    entityProperty.SetValue(viewModel, item, BindingFlags.FlattenHierarchy, null, null, null);
                    viewModelsList.Add(viewModel);
                }
            }

            return viewModelsList;
        }

        private bool IsViewModelList(object value)
        {
            return (value is IList &&
                    value.GetType().IsGenericType &&
                    typeof(IPampaViewModel).IsAssignableFrom(value.GetType().GetGenericArguments()[0]));
        }

        private void SetPropertyViewModelList(object value, string propertyID)
        {
            var viewModelItemType = value.GetType().GetGenericArguments()[0];
            var entityProperty = viewModelItemType.GetProperty("Entity");

            var viewModelsList = (IList)value;
            var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { entityProperty.PropertyType }));

            foreach (var viewModel in viewModelsList)
            {
                var entity = entityProperty.GetValue(viewModel, BindingFlags.FlattenHierarchy, null, null, null);
                list.Add(entity);
            }

            EntityManager.SetPropertyValue(_entity, propertyID, list);
        }

        #endregion
    }
}
